#include <Streaming.h>
#include <Time.h>  
#include <LiquidCrystal.h>
#include <OneWire.h>
#include <DallasTemperature.h>

//#define UNO
#define MEGA
//#define DEBUG_PH
//#define DEBUG_LIGHT
#define ledTestPin 13
#define ledLightPin 10
#define tempSensorPin 45
#define relaisPin1 34
#define relaisPin2 35
#define relaisPin3 36
#define relaisPin4 37
#define buzzerPin 9

// Alarm
const long interval = 5000;           // interval in milliseconds
long previousMillis = 0;
// PH
const float pHMax = 6.85;
const float pHMin = 6.65;
const float pHMaxAlarm = 8.5;
const float pHMinAlarm = 5.5;
const float pHCalibrationValue = 0.0;
//time settings
int ledMinutes[8];
const float ledTime[] = {
  7.00, 7.30, 10.00, 10.30, 18.00, 18.30, 20.00, 20.30}; // time
const int    ledInt[] = {
  0,    100,  100,   8,     8,     100,   100,   0}; // light intensity (0 - 100%)
const int ledTimeCount = 8; 
const int turnCO2onLight = 1; 

#define TIME_MSG_LEN  11   // time sync to PC is HEADER followed by unix time_t as ten ascii digits
#define TIME_REQUEST  7    // ASCII bell character requests a time sync message 

// initialize the library with the numbers of the interface pins
#ifdef UNO
LiquidCrystal LCD(8, 9, 4, 5, 6, 7);
#endif
#ifdef MEGA
LiquidCrystal LCD(12, 11, 4, 5, 6, 7);
#endif
// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(tempSensorPin);
// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

void setup()  {
  pinMode(ledTestPin, OUTPUT);  //we'll use the debug LED to output a heartbeat
  //Buzzer
  pinMode(buzzerPin, OUTPUT);
  // Relais
  pinMode(relaisPin1, OUTPUT);
  pinMode(relaisPin2, OUTPUT);
  pinMode(relaisPin3, OUTPUT);
  pinMode(relaisPin4, OUTPUT);
  // set up the LCD's number of columns and rows: 
#ifdef UNO
  LCD.begin(16, 2);
#endif
#ifdef MEGA
  LCD.begin(20, 4);
#endif
  // LED
#ifdef MEGA
  analogWrite(ledLightPin, 0);
#endif
  fillLedTimeArray();  
  Serial.begin(9600);
  setSyncProvider( requestSync);  //set function to call when sync required
  // pH stamp 
  Serial1.begin(38400); // Pin 18, 19
  Serial1.print("L0");
  Serial1.write(13);
}

void loop(){   
  blinkTestLed(); 
  readUserCommands();
  if(Serial.available() ) 
  {
    processSyncMessage();
  }
  if(timeStatus()!= timeNotSet) 
  {
    digitalClockDisplay();  
  }
  // set light
  int light = 0;
#ifdef TEST_LIGHT
  for (int i = 7; i < 24; i++)  {
    for (int j = 0; j < 60; j++)  {
      light = computeLight(i, j);
      setLight(light);
      printLight(light);
    }
  }
#else
  light = computeLight(hour(), minute());
  setLight(light);
  printLight(light);
#endif
  // show temperature
  float tempC = getTemp();
  printTemp(tempC);
  float ph = getPH(tempC);
#ifdef DEBUG_PH
  Serial << "getPH() =" << _FLOAT(ph,2) << endl;
#endif
  printPH(ph);
  checkPh(ph);
  boolean state = turnPhRelais(ph, light);
  printPhRelaisStatus(state);
}

boolean turnPhRelais(float pHValue, int light)
{
  if(light >= turnCO2onLight) {
    int tmpPh = pHValue*100;
    int tmpPhMax = pHMax*100;
    int tmpPhMin = pHMin*100;
    // CO2 einschalten oder ausschalten
    if(tmpPh >= tmpPhMax) {
#ifdef DEBUG_PH
      Serial << _DEC(tmpPh) << " >= " << tmpPhMax << endl;
#endif
      digitalWrite(relaisPin1, HIGH);
      return true;
    }
    else if(tmpPh <= tmpPhMin) {
#ifdef DEBUG_PH
      Serial << _DEC(tmpPh) << "PH <= " << tmpPhMin << endl;
#endif
      digitalWrite(relaisPin1, LOW);
      return false;
     }
  } 
  else {
    digitalWrite(relaisPin1, LOW);
    return false;
  }  
}

void printPhRelaisStatus(boolean state)
{
  LCD.setCursor(8, 2);
  if(state)
    LCD << "CO2 is ON ";
  else
    LCD << "CO2 is OFF";
}

void printPH(float pHValue)
{
  LCD.setCursor(0, 2);
  LCD << "PH " << _FLOAT(pHValue, 2) << " ";
}

float getPH(float temp)
{
  float pHValue = 0.0;
  float calPHValue = 0.0;
  Serial1.flush();
  delay(100);
  // ph stamp a new command
  if(temp > 0)
  {
    // temperatur sensor nicht angeschlossen
    Serial1.print(temp, DEC);  // send temperatur or "r". nicht beides!
    Serial1.write(13); // ende
#ifdef DEBUG_PH
    Serial << "set temp on ph = " << _FLOAT(temp, 1) << endl;
#endif
  }
  else
  {
    Serial1.print("r");  // send temperatur or "r". nicht beides!
    Serial1.write(13); // ende
  }
  delay(1000);

  byte holding=Serial1.available();            //lets read how many bytes have been received
#ifdef DEBUG_PH
  Serial << _DEC(holding) << " byte received from PH stamp" << endl;
#endif

  if(holding > 4 && holding <=12) {  //if we see the more than three bytes have been received by the Arduino
    char data[12];          //this is where the data from the stamp is stored. if no pH probe is connected, the message "check probe" is transmitted.
    for(byte i=0; i <= holding-1;i++) {  //we make a loop that will read each byte we received
      data[i]= char(Serial1.read());     //and load that byte into the stamp_data array
    }
#ifdef DEBUG_PH
    Serial.print("PH=");
    for(byte i=0; i <= holding-1;i++) {  //we now loop through the array
      Serial.print(data[i]);             //printing each byte we received  through the hardware UART
    }
    Serial.println(""); 
#endif
    if (atoi(data) != 0) {
      pHValue = atof(data);
    }
    else 
      pHValue = 0;

    if(pHValue > 0) calPHValue = pHValue + pHCalibrationValue;
  }
  else if(holding == 0)
  {
    Serial.println("Error: No data from PH probe");
  }
  else if(holding > 12)
  {
    Serial.println("Error: Too many data from PH probe. Max 12 is allowed.");
  }
  return calPHValue;
}

void checkPh(float phValue)
{
  // ph alarm
  if(phValue == 0)
  {
    LCD.setCursor(0,3);
    LCD.print("Check PH probe");
  }
  else if(phValue <= pHMinAlarm || phValue >= pHMaxAlarm)
  {
    Alarm("Check PH Value");
  }
  else
    ClearAlarmMessage();
}

/************************* temperatur functions ************************************/
int getTemp()
{
  DeviceAddress insideThermometer; // arrays to hold device address
  sensors.requestTemperatures(); // send the command to get temperatures
  float tempC = sensors.getTempC(insideThermometer);
  if(tempC == -127) {
    // Start up the library
    sensors.begin();
    if (!sensors.getAddress(insideThermometer, 0)) 
    {
#ifdef DEBUG_TEMP
      Serial.println("Unable to find address for Device 0"); 
#endif
    }
    // set the resolution to 9 bit (Each Dallas/Maxim device is capable of several different resolutions)
    sensors.setResolution(insideThermometer, 8);
    sensors.requestTemperatures(); // Send the command to get temperatures
    tempC = sensors.getTempC(insideThermometer);
  }
  return tempC;
}

void printTemp(float temp)
{
  LCD.setCursor(12, 1);
  LCD << _FLOAT(temp, 1) << " C  ";
#ifdef DEBUG_TEMP
  Serial << "Temp = " << _FLOAT(temp, 1) << "C" << endl;
#endif  
}

int computeLight(int hour, int minute) {
  int light = 0;
  int startIndex = -1;
  int endIndex = -1;
  int minutes = (hour * 60) + minute;
#ifdef DEBUG_LIGHT 
  Serial << "Set time " << _DEC(hour) << ":" << _DEC(minute) << " (" << minutes << ")" << endl; 
#endif 
  // get start range
  for (int i = 0; i < ledTimeCount; i++)  {
    float def_minutes = ledMinutes[i];
    if(minutes >= def_minutes) {
      startIndex = i;
    }
  }
  if(startIndex == -1) startIndex = ledTimeCount - 1;
  // get end range
  endIndex = startIndex+1;
  if(startIndex == ledTimeCount-1) endIndex = 0;
#ifdef DEBUG_LIGHT 
  Serial << "Range " << _DEC(startIndex) << "-" << _DEC(endIndex) << " T(" << ledMinutes[startIndex] << "-" << ledMinutes[endIndex] << ") L("<< ledInt[startIndex] << "-" << ledInt[endIndex] << ")" << endl;
#endif
  // calculate ligth
  if(ledInt[endIndex] == ledInt[startIndex])
  {
    light = ledInt[endIndex];
  }
  else if(ledInt[endIndex] != 0 || ledInt[startIndex] != 0)
  {
    // tangens
    int b_range = abs(ledMinutes[endIndex] - ledMinutes[startIndex]);
    int a_range = abs(ledInt[endIndex] - ledInt[startIndex]);
    int a_min = min(ledInt[endIndex], ledInt[startIndex]);
    float alfa = (float)a_range/(float)b_range;
#ifdef DEBUG_LIGHT
    Serial << "b_range " << _DEC(b_range) << " a_range " << _DEC(a_range) << " tangens " << _FLOAT(alfa,4) << endl;
#endif
    // current ligth
    if(minutes >= ledMinutes[startIndex])
    {
      int b = minutes - ledMinutes[startIndex];
      light = a_min + b*alfa;
      if(ledInt[endIndex] < ledInt[startIndex]) light = ledInt[startIndex] - light;
#ifdef DEBUG_LIGHT
      Serial << "b " << b << endl;
#endif
    }
  }
  return light;
}

void setLight(int light) 
{
  int pwm = 255 - (int)light*2.55;
  if(light == 100) pwm = 0;
  if(pwm > 255) pwm = 255;
  analogWrite(ledLightPin, pwm);
}

void printLight(int light)
{
  char text[10];
  sprintf(text, "Light %03d%%", light);
  LCD.setCursor(0,1);
  LCD.print(text);
}

void fillLedTimeArray() {
  for (int i = 0; i < ledTimeCount; i++)  {
    float time = ledTime[i]; 
    int minits = 0;
    minits = ((int) time) * 60;
    time = (time - (int)time) * 100;
    minits += ((int) time);
    ledMinutes[i] = minits;
#ifdef DEBUG_LIGHT
    Serial << ledTime[i] << "-" << minits << endl;
#endif
  }
}

void digitalClockDisplay(){
  char text[20];
#ifdef MEGA
  sprintf(text, "Time %02d-%02d %02d:%02d:%02d", day(), month(), hour(), minute(), second());
#else
  sprintf(text, "%02d-%02d %02d:%02d:%02d", day(), month(), hour(), minute(), second());
#endif
  LCD.setCursor(0,0);
  LCD.print(text);
}

void processSyncMessage() {
  // if time sync available from serial port, update time and return true
  while(Serial.available() >=  TIME_MSG_LEN ){  // time message consists of a header and ten ascii digits
    char c = Serial.read() ; 
    Serial.print(c);  
    if( c == 'T' ) {       
      time_t pctime = 0;
      for(int i=0; i < TIME_MSG_LEN -1; i++){   
        c = Serial.read();          
        if( c >= '0' && c <= '9'){   
          pctime = (10 * pctime) + (c - '0') ; // convert digits to a number    
        }
      }   
      setTime(pctime);   // Sync Arduino clock to the time received on the serial port
    }  
  }
}

time_t requestSync()
{
  Serial.write(TIME_REQUEST);  
  return 0; // the time will be sent later in response to serial mesg
}

void Alarm(String text)
{
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillis > interval) {
    // save the last time you blinked the LED 
    previousMillis = currentMillis;   
    Buzzer(buzzerPin, 1400, 100); // buzz the buzzer on pin 9 at 1500Hz for 100 milliseconds
  }
  LCD.setCursor(0, 3);
  LCD.print(text);  
}

void ClearAlarmMessage()
{
  LCD.setCursor(0, 3);
  LCD.print("                    "); 
}

void Buzzer(int targetPin, long frequency, long length) {
  long delayValue = 1000000/frequency/2; // calculate the delay value between transitions
  // 1 second's worth of microseconds, divided by the frequency, then split in half since
  // there are two phases to each cycle
  long numCycles = frequency * length/ 1000; // calculate the number of cycles for proper timing
  // multiply frequency, which is really cycles per second, by the number of seconds to 
  // get the total number of cycles to produce
  for (long i=0; i < numCycles; i++){ // for the calculated length of time...
    digitalWrite(targetPin,HIGH); // write the buzzer pin high to push out the diaphram
    delayMicroseconds(delayValue); // wait for the calculated delay value
    digitalWrite(targetPin,LOW); // write the buzzer pin low to pull back the diaphram
    delayMicroseconds(delayValue); // wait againf or the calculated delay value
  }
}

void blinkTestLed()
{
  if(digitalRead(ledTestPin))
    digitalWrite(ledTestPin, LOW); 
  else
    digitalWrite(ledTestPin, HIGH); 
}

void readUserCommands()
{
  if (Serial.available() > 0) {
    int inByte = Serial.read();

    switch (inByte) {
    case 'h':
      Serial.println("h - Help");
      Serial.println("7 - Place pH probe in the pH 7 calibration solution and wait 1 to 2 minutes, before do it");
      Serial.println("4 - Place pH probe in the pH 4 calibration solution and wait 1 to 2 minutes, before do it");
      Serial.println("r - single pH reading");
      Serial.println("i - Instructs the pH-Stamp to transmit it version number");
      Serial.println("x - Instructs the pH-Stamp to return to its original factory settings.");
      break;
    case '7':
      transmittingCommand('s');
      break;
    case '4':
      transmittingCommand('F');
      break;
    case 'r':
      transmittingCommand('r');
      break;
    case 'i':
      transmittingCommand('i');
      break;
    case 'x':
      transmittingCommand('x');
      break;      
    default:
      break;
    }
  }
}

void transmittingCommand(char command)
{
  Serial1.flush();
  delay(100);
  Serial1.print(command);  
  Serial1.write(13);
  delay(1000);

  byte holding=Serial1.available();            //lets read how many bytes have been received
  if(holding > 0) {  //if we see the more than three bytes have been received by the Arduino
    char data[5];          //this is where the data from the stamp is stored. if no pH probe is connected, the message "check probe" is transmitted.
    for(byte i=0; i <= holding-1;i++) {  //we make a loop that will read each byte we received
      data[i]= char(Serial1.read());     //and load that byte into the stamp_data array
    }
    Serial.print("Command return: ");
    for(byte i=0; i <= holding-1;i++) {  //we now loop through the array
      Serial.print(data[i]);             //printing each byte we received  through the hardware UART
    }
    Serial.println(""); 
  }
}





